package cfca.seal.web.action.seal;

import cfca.sadk.algorithm.sm2.SM2PrivateKey;
import cfca.sadk.lib.crypto.JCrypto;
import cfca.sadk.lib.crypto.Session;
import cfca.sadk.util.Base64;
import cfca.sadk.util.CertUtil;
import cfca.sadk.util.Signature;
import cfca.sadk.x509.certificate.X509CRL;
import cfca.sadk.x509.certificate.X509Cert;
import cfca.seal.bean.*;
import cfca.seal.common.Page;
import cfca.seal.exception.CodeException;
import cfca.seal.maker.bean.*;
import cfca.seal.maker.util.SealExtracter;
import cfca.seal.maker.util.WebSeal;
import cfca.seal.maker.util.WebSealUtil;
import cfca.seal.mananger.CrlManager;
import cfca.seal.mananger.OperationLogManager;
import cfca.seal.util.*;
import cfca.seal.web.service.OperationLogService;
import cfca.seal.web.service.OrganizationService;
import cfca.seal.web.service.SealCertService;
import cfca.seal.web.service.SealService;
import cfca.seal.web.system.SessionManager;
import cfca.seal.web.vo.PdfSealVo;
import cfca.seal.web.vo.WebSealVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.security.PrivateKey;
import java.util.Date;

@Controller
@RequestMapping({"/seal"})
public class SealAction
{
  private String jumpListPagePath = "sys/seal/list.html";
  private String jumpDetailPagePath = "sys/seal/detail.html";
  private String jumpAddPagePath = "sys/seal/add.html";
  private String jumpBindPagePath = "sys/seal/bind.html";
  private String jumplistdo = "redirect:/seal/v_list.do";
  private String jumpPdfSealPagePath = "sys/seal/pdfseal.html";
  private String jumpPWebSealPagePath = "sys/seal/webseal.html";
  private String applicationName = "印章管理";

  @Resource
  private SealService sealService;

  @Resource
  private SealCertService sealCertService;

  @Resource
  private OperationLogService operationLogService;

  @Resource
  private OrganizationService organizationService;
  private static final Logger logger = LoggerFactory.getLogger("web_sys");

  @RequestMapping({"/v_list"})
  public String getAll(Seal seal, String pageNo, ModelMap modelMap, HttpServletRequest request) throws CodeException {
    try {
      AdminUser manager = SessionManager.getManager(request);
      Page page = this.sealService.getSeals(seal, pageNo, manager);
      modelMap.put("page", page);
      modelMap.put("seal", seal);
      return this.jumpListPagePath;
    } catch (Exception e) {
      logger.error("查看印章列表出现系统错误", e);
      throw new CodeException("C0001", e);
    }
  }

  @RequestMapping({"/v_pdfseal"})
  public String viewPdfSealPage(String pageNo, ModelMap modelMap, HttpServletRequest request) throws CodeException {
    return this.jumpPdfSealPagePath;
  }

  @RequestMapping({"/v_webseal"})
  public String viewWebSealPage(ModelMap modelMap, HttpServletRequest request) throws CodeException {
    WebSealVo webSignVo = new WebSealVo();
    request.setAttribute("inputBean", webSignVo);
    modelMap.put("keyType", Integer.valueOf(1));
    return this.jumpPWebSealPagePath;
  }

  @RequestMapping({"/v_add"})
  public String viewAddPage(ModelMap modelMap) throws CodeException {
    Seal seal = new Seal();
    seal.setId(StringUtil.generateID());
    modelMap.put("seal", seal);
    return this.jumpAddPagePath;
  }

  @RequestMapping({"/v_bind"})
  public String viewEditPage(String sealCode, String bindId, ModelMap modelMap) throws CodeException {
    try {
      String userId = this.sealService.getBindUser(bindId);
      modelMap.put("sealId", bindId);
      modelMap.put("code", sealCode);
      modelMap.put("bindId", userId);
      return this.jumpBindPagePath;
    } catch (Exception e) {
      logger.error("绑定用户出现系统错误", e);
      throw new CodeException("C0001", e);
    }
  }

  @RequestMapping({"/v_detail"})
  public String viewDetailPage(String viewId, ModelMap modelMap, HttpServletRequest request)
    throws CodeException
  {
    try
    {
      Seal seal = this.sealService.getSealById(viewId);
      X509Cert x509Cert = null;
      String unitName = "";
      String signerName = "";
      String certType = "RSA";
      if (0 == seal.getCertType()) {
        SealExtracter sEx = new SealExtracter(seal.getSealData(), EncryptUtil.decrypto(seal.getSealPfxPwd()));
        int keyType = sEx.getKeyType();
        if (1 == keyType) {
          x509Cert = CertUtil.getCertFromPFX(seal.getSealPfxData(), EncryptUtil.decrypto(seal.getSealPfxPwd()));
        } else if (2 == keyType) {
          x509Cert = CertUtil.getCertFromSM2(seal.getSealPfxData());
          certType = "SM2";
        }

        unitName = sEx.getUnitName();
        signerName = sEx.getSignerName();
      } else if ((2 == seal.getCertType()) || (1 == seal.getCertType())) {
        SealCert sealCert = this.sealCertService.getSealCertById(seal.getSealCertId());
        x509Cert = new X509Cert(sealCert.getPublickCertData());
      }
      if (null != x509Cert) {
        modelMap.put("dn", x509Cert.getSubject());
        modelMap.put("notBefore", x509Cert.getNotBefore());
        modelMap.put("notAfter", x509Cert.getNotAfter());
      }
      modelMap.put("certType", certType);
      modelMap.put("unitName", unitName);
      modelMap.put("signerName", signerName);
      modelMap.put("seal", seal);
      return this.jumpDetailPagePath;
    } catch (Exception e) {
      logger.error("查看印章信息出现系统错误", e);
      throw new CodeException("C0001", e);
    }
  }

  @RequestMapping({"/o_add"})
  public String add(@RequestParam("sealPfxFile") CommonsMultipartFile sealPfxFile, Seal seal, ModelMap modelMap, HttpServletRequest request)
    throws CodeException
  {
    OperationLog operationLog = OperationLogManager.getInstance(request, this.applicationName, "增加", seal.getCode(), "增加印章信息", null);
    operationLog.setOrganizationId(SessionManager.getManager(request).getOrganizationId());
    try
    {
      if (0 == seal.getCertType()) {
        if ((!sealPfxFile.getFileItem().getName().toLowerCase().endsWith(".pfx")) && (!sealPfxFile.getFileItem().getName().toLowerCase().endsWith(".sm2"))) {
          throw new CodeException("C1005", "上传的文件必须为.pfx或者.sm2格式！");
        }

        byte[] sealpfxData = "".getBytes();
        sealpfxData = FileUtil.getBytesFromInputStream(sealPfxFile.getInputStream());
        seal.setSealPfxData(sealpfxData);
      } else if (1 == seal.getCertType()) {
        seal.setSealCertId(seal.getSealCertIdHard());
      } else if (2 == seal.getCertType()) {
        seal.setSealCertId(seal.getSealCertIdSoft());
      }
      seal.setCreator(SessionManager.getId(request));
      seal.setStatus(1);
      seal.setCreateTime(new Date());
      boolean resultInfo = this.sealService.save(seal);
      modelMap.put("resultInfo", Boolean.valueOf(resultInfo));
      return this.jumplistdo;
    } catch (CodeException ce) {
      operationLog.setResult(10);
      logger.error("制作印章出现错误", ce);
      throw ce;
    } catch (Exception e) {
      operationLog.setResult(10);
      logger.error("制作印章出现系统错误", e);
      throw new CodeException("C0001", "制作印章失败", e);
    } finally {
      this.operationLogService.addOperationLog(operationLog);
    }
  }

  @RequestMapping({"/o_bind"})
  public String bind(String sealId, String code, String bindId, ModelMap modelMap, HttpServletRequest request) throws CodeException {
    OperationLog operationLog = OperationLogManager.getInstance(request, this.applicationName, "绑定用户", code, "修改印章绑定用户", null);
    operationLog.setOrganizationId(SessionManager.getManager(request).getOrganizationId());
    try {
      boolean result = this.sealService.bindUser(sealId, bindId);
      modelMap.put("resultInfo", Boolean.valueOf(result));
      return this.jumplistdo;
    } catch (Exception e) {
      operationLog.setResult(10);
      logger.error("绑定用户出现系统错误", e);
      throw new CodeException("C0001", e);
    } finally {
      this.operationLogService.addOperationLog(operationLog);
    }
  }

  @RequestMapping({"/o_updateStatus"})
  public String updateStatus(String id, String code, String status, ModelMap modelMap, HttpServletRequest request) throws CodeException {
    OperationLog operationLog = OperationLogManager.getInstance(request, this.applicationName, "修改", code, "修改印章状态", null);
    operationLog.setOrganizationId(SessionManager.getManager(request).getOrganizationId());
    try
    {
      if (status.equals(String.valueOf(0))) {
        status = String.valueOf(1);
        operationLog.setDescription(operationLog.getDescription() + "(" + "启用" + ")");
      } else {
        status = String.valueOf(0);
        operationLog.setDescription(operationLog.getDescription() + "(" + "禁用" + ")");
      }
      boolean resultInfo = this.sealService.updatestatus(id, status);
      modelMap.put("resultInfo", Boolean.valueOf(resultInfo));
      return this.jumplistdo;
    } catch (Exception e) {
      operationLog.setResult(10);
      logger.error("更新印章状态出现系统错误", e);
      throw new CodeException("C0001", e);
    } finally {
      this.operationLogService.addOperationLog(operationLog);
    }
  }

  @RequestMapping({"/o_webseal"})
  public String webSeal(WebSealVo webSignVo, ModelMap modelMap, HttpServletRequest request) throws CodeException
  {
    OperationLog operationLog = OperationLogManager.getInstance(request, "WEB签章", "签章", "", "WEB签章", null);
    operationLog.setOrganizationId(SessionManager.getManager(request).getOrganizationId());
    try
    {
      Seal seal = this.sealService.getSealById(webSignVo.getSealId());
      checkSealInfo(webSignVo.getSealPasswd(), seal);
      operationLog.setObjectId(seal.getCode());

      byte[] signData = "".getBytes();
      WebSeal webSeal = new WebSeal();
      int keyType = 1;
      SealCert sealCert;
      if (0 == seal.getCertType()) {
        SignResult signResult = WebSealUtil.getSignResultFromSeal(seal.getSealData(), EncryptUtil.decrypto(seal.getSealPwd()), EncryptUtil.decrypto(seal.getSealPfxPwd()), seal.getSealImageData(), webSignVo.getSignLocation(), webSignVo.getSignReason());

        signData = webSeal.signWebSeal(signResult, webSignVo.getSourceData().getBytes(), EncryptUtil.decrypto(seal.getSealPfxPwd()));
        if (SealCertUtil.isSM2(seal.getSealPfxData()))
          keyType = 2;
      } else if ((1 == seal.getCertType()) || (2 == seal.getCertType())) {
        sealCert = this.sealService.getSealCertById(seal.getSealCertId());
        X509Cert cert = new X509Cert(sealCert.getPublickCertData());

        JCrypto.getInstance().initialize(sealCert.getDevice(), null);
        Session session = JCrypto.getInstance().openSession(sealCert.getDevice());

        boolean certType = true;
        PrivateKey privateKey = null;
        if (sealCert.getDevice().equals("JHARD_LIB")) {
          privateKey = JHARDUtil.getPrivateKeyFromHard(sealCert.getKeyId(), session);
        } else if (sealCert.getDevice().equals("JSOFT_LIB")) {
          sealCert.decryptCiphertext(sealCert);
          if ("SHA1RSA".equals(sealCert.getCertAlg())) {
            privateKey = (PrivateKey)SealCertUtil.getRSAPrivateKey(Base64.decode(sealCert.getPrivateKeyData()));
            certType = true;
          } else {
            privateKey = SM2PrivateKey.getInstance(Base64.decode(sealCert.getPrivateKeyData()));
            certType = false;
          }
        }

        Organization org = this.organizationService.getOrganizationById(SessionManager.getManager(request).getOrganizationId());
        UsbKeySeal usbKeySeal = new UsbKeySeal(cert, seal.getSealImageData(), SessionManager.getManager(request).getName(), org.getName(), seal.getName());

        SignInfo signInfo = new SignInfo(webSignVo.getSignLocation(), webSignVo.getSignReason());
        SignResult signResult = new SignResult(usbKeySeal, signInfo);

        SoftSeal softSeal = new SoftSeal(signResult.getUsbKeySeal(), signInfo.getVersion());
        SignSource signSource = new SignSource(softSeal, signInfo, webSignVo.getSourceData().getBytes());
        byte[] sourceDatas = webSeal.generateSignedSource(signSource);

        Signature signature = new Signature();
        byte[] p7data = signature.p7SignMessageDetach("sha1WithRSAEncryption", sourceDatas, privateKey, cert, session);

        signData = webSeal.signWebSeal(signResult, webSignVo.getSourceData().getBytes(), Base64.decode(p7data), certType);
      }

      webSignVo.setSignResult(new String(signData));
      request.setAttribute("inputBean", webSignVo);
      modelMap.put("keyType", Integer.valueOf(keyType));
      return this.jumpPWebSealPagePath;
    } catch (CodeException ce) {
      operationLog.setResult(10);
      logger.error("WEB签章失败", ce);
      throw new CodeException(ce.getCode(), ce.getMessage());
    } catch (Exception e) {
      operationLog.setResult(10);
      logger.error("WEB签章失败", e);
      throw new CodeException("C0001", "WEB签章失败", e);
    } finally {
      this.operationLogService.addOperationLog(operationLog);
    }
  }

  @RequestMapping({"/o_pdfseal"})
  public void pdfSeal(@RequestParam("pdfFile") CommonsMultipartFile pdfFile, PdfSealVo pdfSealVo, ModelMap modelMap, HttpServletRequest request, HttpServletResponse response)
    throws CodeException
  {
    OperationLog operationLog = OperationLogManager.getInstance(request, "PDF签章", "签章", "", "PDF签章", null);
    operationLog.setOrganizationId(SessionManager.getManager(request).getOrganizationId());
    Seal seal = new Seal();
    try {
      seal = this.sealService.getSealById(pdfSealVo.getSealId());
      checkSealInfo(pdfSealVo.getSealPwd(), seal);
      operationLog.setObjectId(seal.getCode());
      operationLog.setFileName(pdfFile.getOriginalFilename());

      if (pdfFile.getOriginalFilename().length() > 240) {
        throw new CodeException("C2005", "上传的PDF文件名不能过长！");
      }

      byte[] sealedPdfData = this.sealService.doSealForPdf(pdfFile.getBytes(), seal, pdfSealVo);
      String filename = "sealed_" + pdfFile.getOriginalFilename();
      response.reset();
      response.setContentType("text/html; charset=utf-8");
      response.addHeader("content-type", "application/x-msdownload;");
      response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
      response.addHeader("Content-Length", "" + sealedPdfData.length);

      OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
      outputStream.write(sealedPdfData);
      outputStream.flush();
      outputStream.close();
    }
    catch (CodeException ce) {
      operationLog.setResult(10);
      logger.error("pdf签章失败", ce);
      throw new CodeException(ce.getCode(), ce.getMessage());
    } catch (Exception e) {
      operationLog.setResult(10);
      logger.error("pdf签章失败", e);
      throw new CodeException("C0001", "pdf签章失败", e);
    } finally {
      operationLog.setObjectId(seal.getCode());
      this.operationLogService.addOperationLog(operationLog);
    }
  }

  @RequestMapping({"/v_showSealImage"})
  public void showSealImage(HttpServletRequest request, HttpServletResponse response) throws Exception {
    Seal seal = this.sealService.getSealById(request.getParameter("id"));
    if (seal != null) {
      byte[] bImage = seal.getSealImageData();
      OutputStream out = response.getOutputStream();
      out.write(bImage);
      out.flush();
      out.close();
    }
  }

  @RequestMapping({"/o_downloadSeal"})
  public void downloadSeal(String id, HttpServletRequest request, HttpServletResponse response) throws CodeException {
    OutputStream outputStream = null;
    try {
      outputStream = new BufferedOutputStream(response.getOutputStream());
      Seal seal = this.sealService.getSealById(id);
      String sealName = seal.getCode() + ".seal";
      byte[] sealData = Base64.encode(seal.getSealData());

      response.reset();
      response.setContentType("text/html; charset=utf-8");
      response.addHeader("content-type", "application/x-msdownload;");
      response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(sealName, "UTF-8"));
      response.addHeader("Content-Length", "" + sealData.length);
      outputStream.write(sealData);
      outputStream.flush();
    } catch (CodeException ce) {
      throw ce;
    } catch (Exception e) {
      throw new CodeException("C0001", "下载电子印章失败", e);
    } finally {
      try {
        if (outputStream != null)
          outputStream.close();
      }
      catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  private void checkSealInfo(String sealPwd, Seal sealBean) throws Exception {
    if (sealBean == null) {
      throw new CodeException("C4004", "没有找到对应的印章");
    }

    if (!EncryptUtil.decrypto(sealBean.getSealPwd()).equals(sealPwd)) {
      throw new CodeException("C1002", "印章密码不正确");
    }

    X509Cert x509Cert = null;
    if (0 == sealBean.getCertType()) {
      SealExtracter sEx = new SealExtracter(sealBean.getSealData(), EncryptUtil.decrypto(sealBean.getSealPfxPwd()));
      int keyType = sEx.getKeyType();
      if (1 == keyType)
        x509Cert = CertUtil.getCertFromPFX(sealBean.getSealPfxData(), EncryptUtil.decrypto(sealBean.getSealPfxPwd()));
      else if (2 == keyType)
        x509Cert = CertUtil.getCertFromSM2(sealBean.getSealPfxData());
    }
    else if ((2 == sealBean.getCertType()) || (1 == sealBean.getCertType())) {
      SealCert sealCert = this.sealCertService.getSealCertById(sealBean.getSealCertId());
      x509Cert = new X509Cert(sealCert.getPublickCertData());
    }

    if (!VerifyUtil.verifyCertDate(x509Cert, TimeUtil.getCurrentTime())) {
      throw new CodeException("C9001", "证书已过期或者还未生效");
    }

    X509CRL x509crl = CrlManager.getX509CRL(StringUtil.formatIssuerKey(x509Cert.getIssuer()));
    if ((x509crl != null) && (VerifyUtil.verifyByCRL(x509Cert, x509crl)))
      throw new CodeException("C9002", "证书已经吊销");
  }
}